#include "CusTextureItem.h"
#include <QSGGeometry>
#include <QQuickWindow>
#include <QSGGeometryNode>
#include <QSGTexture>
#include <QSGTextureMaterial>
#include <QSGMaterialShader>
#include "tool.h"

class CusTextureShader : public QSGMaterialShader
{
public:
    CusTextureShader()
    {
        auto vs =  getResPath(qmlPath + QStringLiteral("cusTexture.vert.qsb"));
        auto fs =  getResPath(qmlPath + QStringLiteral("cusTexture.frag.qsb"));

        setShaderFileName(VertexStage, vs);
        setShaderFileName(FragmentStage, fs);
    }
    virtual bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
    virtual void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
class CusTextureMaterial : public QSGMaterial
{
public:
    CusTextureMaterial()
    {
        setFlag(Blending);
    }
    ~CusTextureMaterial()
    {
        delete mTexture;
    }
    QSGTexture *mTexture = nullptr;
public:
    virtual QSGMaterialType *type() const override
    {
        static QSGMaterialType t;
        return &t;
    }
    virtual QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override
    {
        return new CusTextureShader;
    }
    virtual int compare(const QSGMaterial *o) const override
    {
        Q_ASSERT(o && type() == o->type());
        const auto *other = static_cast<const CusTextureMaterial *>(o);
        if(other == this )
        {
            return 0;
        }
        if (!mTexture || !other->mTexture)
        {
            return mTexture ? 1 : -1;
        }
        if (int diff = mTexture->comparisonKey() - other->mTexture->comparisonKey())
        {
            return diff;
        }
        return 0;
    }
};
bool  CusTextureShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
    bool changed = false;
    QByteArray *buf = state.uniformData();
    Q_ASSERT(buf->size() >= 68);
    if (state.isMatrixDirty())
    {
        const auto &m = state.combinedMatrix();
        memcpy(buf->data(), m.constData(), 64);
        changed = true;
    }
    if (state.isOpacityDirty())
    {
        const float opacity = state.opacity();
        memcpy(buf->data() + 64, &opacity, 4);
        changed = true;
    }
    return changed;
}
void CusTextureShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
    (void) state;
    (void)binding;

    auto material = static_cast<CusTextureMaterial *>(newMaterial);

    //reference from qt6.2.2 opensource
    material->mTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());

    *texture = material->mTexture;
}

class CusTextureNode : public QSGGeometryNode
{
public:
    CusTextureNode(QQuickWindow *pWin)
    {
        auto path = getImagePath( imgPath + QStringLiteral("qz.png"));
        QImage image(path);
        qWarning() << path << image.width() << image.height();

        auto t = pWin->createTextureFromImage(image);
        t->setFiltering(QSGTexture::Nearest);
        t->setHorizontalWrapMode(QSGTexture::Repeat);
        t->setVerticalWrapMode(QSGTexture::Repeat);

        mMaterial = new CusTextureMaterial;
        mMaterial->mTexture = t;
        setMaterial(mMaterial);
        setFlag(OwnsMaterial);

        mGeo = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
        QSGGeometry::updateTexturedRectGeometry(mGeo, QRect(), QRect());
        setGeometry(mGeo);
        setFlag(OwnsGeometry);
    }
    void setRect(const QRectF &bounds)
    {
        QSGGeometry::updateTexturedRectGeometry(mGeo, bounds, QRectF(0, 0, 1, 1));
        markDirty(QSGNode::DirtyGeometry);
    }
private:
    QSGGeometry *mGeo;
    CusTextureMaterial *mMaterial;
};

CusTextureItem::CusTextureItem(QQuickItem* parent)
    : Super(parent)
{
    setFlag(Super::ItemHasContents);

    connect(this, &QQuickItem::xChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
    connect(this, &QQuickItem::yChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
    connect(this, &QQuickItem::widthChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
    connect(this, &QQuickItem::heightChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
}

CusTextureItem::~CusTextureItem()
{
}

QSGNode* CusTextureItem::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* data)
{
    (void)data;
    auto node = static_cast<CusTextureNode*>(oldNode);
    if (!node) {
        node = new CusTextureNode(window());
    }
    if (mGeometryChanged)
    {
        node->setRect(boundingRect());
        mGeometryChanged = false;
    }
    return node;
}
